package org.jbrisk;

import java.util.HashMap;

import org.jbrisk.exceptions.NullArgumentException;

public class ServiceContainer implements IServiceContainer {

	private boolean multiThread;

	public boolean isMultiThread() {
		return multiThread;
	}

	public void setMultiThread(boolean multiThread) {
		this.multiThread = multiThread;
	}

	/**
	 * Single thread services list.
	 */
	private HashMap<String, Object> singleThreadServices;

	/**
	 * Multi thread services list.
	 */
	private static final ThreadLocal<HashMap<String, Object>> multiThreadServices = new ThreadLocal<HashMap<String, Object>>();

	protected HashMap<String, Object> getServices() {

		HashMap<String, Object> svcs = null;

		if (isMultiThread()) {

			svcs = multiThreadServices.get();

		} else {

			svcs = singleThreadServices;
		}

		return svcs;
	}

	protected void setServices(HashMap<String, Object> services) {

		if (multiThread) {
			
			multiThreadServices.set(services);
			
		} else {
			
			this.singleThreadServices = services;
		}
	}

	public <T> void removeService(Class<T> svcClass) {

		if (svcClass == null)
			throw new NullArgumentException("svcClass");

		if (!hasService(svcClass))
			throw createClassServiceNotFoundException(svcClass);

		getServices().remove(svcClass.getName());
	}

	public <T> void addService(Class<T> svcClass, T svc) {

		addService(svcClass, svc, false);
	}

	public <T> void addService(Class<T> svcClass, T svc, boolean replaceExisting) {

		if (svcClass == null)
			throw new NullArgumentException("svcClass");

		if (svc == null)
			throw new NullArgumentException("svc");

		String svcKey = svcClass.getName();

		HashMap<String, Object> svcs = getServices();

		if (svcs == null) {

			svcs = new HashMap<String, Object>();

			setServices(svcs);

		} else if (!replaceExisting && svcs.containsKey(svcKey)) {

			throw new IllegalArgumentException("A service for the class \"" + svcKey
					+ "\" was already added! If you want to replace it, please remove it first.");
		}

		svcs.put(svcKey, svc);
	}

	public boolean hasService(Class<?> svcClass) {

		if (svcClass == null)
			throw new NullArgumentException("svcClass");

		return getService(svcClass, false) != null;
	}

	public <T> T getService(Class<T> svcClass) {

		if (svcClass == null)
			throw new NullArgumentException("svcClass");

		return getService(svcClass, true);
	}

	@SuppressWarnings("unchecked")
	public <T> T getService(Class<T> svcClass, boolean throwError) {

		String svcKey = svcClass.getName();

		T svc = null;

		HashMap<String, Object> svcs = getServices();

		if (svcs != null && svcs.containsKey(svcKey)) {

			Object untypedSvc = svcs.get(svcKey);

			if (!svcClass.isAssignableFrom(untypedSvc.getClass()))
				throw new ClassCastException(
						"The service for the class \""
								+ svcKey
								+ "\" its not from the expected type. This means that a service that does not extends or implements the supplied class was added in its place!");

			svc = (T) untypedSvc;

		} else {

			// If the service was not found but this instance has a parent
			// IServiceContainer, look for the service on it
			IServiceContainer parentSvcContainer = getParentServiceContainer();

			if (parentSvcContainer != null) {

				svc = parentSvcContainer.getService(svcClass, false);
			}
		}

		if (svc == null && throwError) {

			throw createClassServiceNotFoundException(svcClass);
		}

		return svc;
	}

	/**
	 * Removes all registered services.
	 */
	public void clear() {

		if (getServices() != null) {

			getServices().clear();
		}
	}

	public IServiceContainer getParentServiceContainer() {

		return null;
	}

	protected <T> IllegalArgumentException createClassServiceNotFoundException(Class<T> svcClass) {

		return new IllegalArgumentException("The service for the class \"" + svcClass.getName() + "\" was not found!");
	}

}
